<!DOCTYPE html>
<html>
  <head>
    <title>
      audiobuffer-resample.html
    </title>
    <script src="../../resources/testharness.js"></script>
    <script src="../../resources/testharnessreport.js"></script>
    <script src="../resources/audit-util.js"></script>
    <script src="../resources/audit.js"></script>
  </head>
  <body>
    <script id="layout-test-code">
      let audit = Audit.createTaskRunner();

      // These are global to make debugging a little easier.
      let context;
      let buffer;
      let source;
      let renderedData;
      let trueData;
      let signalEnergy;
      let noiseEnergy;
      let maxError;

      // Context sample rate.
      let sampleRate = 48000;
      // The sample rate of the buffer.
      let bufferRate = 3000;
      // The audio buffer is a sine wave of this frequency.
      let toneFrequency = 440;
      // How long test is
      let lengthInSeconds = 0.5;
      // The maximum allowed peak error between the actual and true output. This
      // value was experimentally determined for the given bufferRate.
      let peakThreshold = 0.11;
      // The minimum SNR allowed between the actual and true output.
      let snrThreshold = 22.35;


      // Generate a sine wave in an AudioBuffer using the given |freq|. The
      // AudioBuffer has the sample rate of |rate|.
      function createSineBuffer(context, freq, rate) {
        let buf = context.createBuffer(1, lengthInSeconds * rate, rate);
        let omega = 2 * Math.PI * freq / rate;
        let signal = buf.getChannelData(0);
        let length = signal.length;
        for (let k = 0; k < length; ++k)
          signal[k] = Math.sin(omega * k);

        return buf;
      }

      // Check the output against the expected output.
      function checkResult(buffer, should) {
        renderedData = buffer.getChannelData(0);
        let length = renderedData.length;
        // Generate a reference sine wave at the context rate
        let trueReference =
            createSineBuffer(context, toneFrequency, context.sampleRate);
        trueData = trueReference.getChannelData(0);

        // To compare the actual output against the reference, we compute the
        // peak error and the SNR between the two.
        signalEnergy = 0;
        noiseEnergy = 0;
        maxError = -1;

        let success = true;

        // Compute the peak error and the SNR.
        for (let k = 0; k < length / 2; ++k) {
          let diff = renderedData[k] - trueData[k];
          noiseEnergy += diff * diff;
          signalEnergy += trueData[k] * trueData[k];
          if (Math.abs(diff) > maxError)
            maxError = Math.abs(diff);
        }

        let snr;

        if (noiseEnergy == 0)
          snr = 1000;
        else
          snr = 10 * Math.log10(signalEnergy / noiseEnergy);

        should(maxError, 'Peak error between actual and reference data')
            .beLessThan(peakThreshold);

        should(snr, 'SNR').beGreaterThan(snrThreshold);
      }

      audit.define('Test resampling of an AudioBuffer', function(task, should) {
        context = new OfflineAudioContext(
            1, lengthInSeconds * sampleRate, sampleRate);

        // Create a sine wave in a buffer that's different from the context rate
        // to test resampling.
        buffer = createSineBuffer(context, toneFrequency, bufferRate);
        source = context.createBufferSource();
        source.buffer = buffer;
        source.connect(context.destination);
        source.start();

        context.startRendering()
            .then(function(buffer) {
              checkResult(buffer, should);
            })
            .then(task.done.bind(task));
      });

      audit.run();
    </script>
  </body>
</html>
